package com.jvm.simulation.hotspot;

import com.jvm.simulation.hotspot.attributes.AttributeInfo;
import lombok.Data;

import java.util.HashMap;

@Data
public class InstanceKlass extends Klass {

    /**
     * ClassFile {
     *     u4             magic; //Class 文件的标志
     *     u2             minor_version;//Class 的小版本号
     *     u2             major_version;//Class 的大版本号
     *     u2             constant_pool_count;//常量池的数量
     *     cp_info        constant_pool[constant_pool_count-1];//常量池
     *     u2             access_flags;//Class 的访问标记
     *     u2             this_class;//当前类
     *     u2             super_class;//父类
     *     u2             interfaces_count;//接口
     *     u2             interfaces[interfaces_count];//一个类可以实现多个接口
     *     u2             fields_count;//Class 文件的字段属性
     *     field_info     fields[fields_count];//一个类会可以有个字段
     *     u2             methods_count;//Class 文件的方法数量
     *     method_info    methods[methods_count];//一个类可以有个多个方法
     *     u2             attributes_count;//此类的属性表中的属性数
     *     attribute_info attributes[attributes_count];//属性表集合
     * }
     */

    private byte[]  magic = new byte[Constants.u4];
    private byte[] minor_version = new byte[Constants.u2];
    private byte[] major_version = new byte[Constants.u2];
    private ContantPool contantPool = new ContantPool();
    private int access_flags;
    private int this_class;
    private int super_class;
    private int interfaces_count;
    private InterfaceInfo[] interfaces;
    private int fields_count;
    private FieldInfo[] field_info;
    private int methods_count;
    private MethodInfo[] method_info;
    private int attributes_count;
    private AttributeInfo[] attribute_info;

    private String className;
    private String fieldName;


    public void setConstant_pool_count(int constant_pool_count) {
        contantPool.setTag(new int[constant_pool_count]);
        contantPool.setContantPool(new HashMap<Integer, Object>());
    }

    public int setConstant_pool( byte[] content, int cnt, int u, int idx) throws Exception {
        byte[] bytes;
        byte[] u1Bytes = new byte[1] ;
        for (int i = 1; i < cnt; i++) {
            System.arraycopy(content,idx,u1Bytes,0, Constants.u1);idx += Constants.u1;
            contantPool.getTag()[i] = u1Bytes[0];
            switch (u1Bytes[0]){
                case Constants.CONSTANT_UTF8:
                    bytes = new byte[Constants.u2];
                    System.arraycopy(content,idx,bytes,
                            0,Constants.u2);idx += Constants.u2;
                    int len = LoadClassFile.getInt(bytes);
                    bytes = new byte[len];
                    System.arraycopy(content,idx,bytes,0,len);idx += len;
                    contantPool.getContantPool().put(i,new String(bytes));
                    break;

                case Constants.CONSTANT_INTEGER:
                    bytes = new byte[Constants.u4];
                    System.arraycopy(content,idx,bytes,0,Constants.u4);idx += Constants.u4;
                    contantPool.getContantPool().put(i,ByteUtil.byteArrayToInt(bytes));
                    break;
                case Constants.CONSTANT_FLOAT:
                    bytes = new byte[Constants.u4];
                    System.arraycopy(content,idx,bytes,0,Constants.u4);idx += Constants.u4;
                    contantPool.getContantPool().put(i,ByteUtil.byteToFloat(bytes));
                    break;

                case Constants.CONSTANT_LONG:
                    bytes = new byte[Constants.u8];
                    System.arraycopy(content,idx,bytes,0,Constants.u8);
                    idx += Constants.u8;
                    contantPool.getContantPool().put(i,ByteUtil.bytesToLong(bytes));

                    contantPool.getTag()[++i] = u1Bytes[0];
                    System.arraycopy(content,idx,bytes,0,Constants.u8);
                    contantPool.getContantPool().put(i,ByteUtil.bytesToLong(bytes));
                    break;

                case Constants.CONSTANT_DOUBLE:

                    bytes = new byte[Constants.u8];
                    System.arraycopy(content,idx,bytes,0,Constants.u8);
                    idx += Constants.u8;
                    contantPool.getContantPool().put(i,ByteUtil.bytesToDouble(bytes,false));
                    contantPool.getTag()[++i] = u1Bytes[0];

                    System.arraycopy(content,idx,bytes,0,Constants.u8);
                    contantPool.getContantPool().put(i,ByteUtil.bytesToDouble(bytes,false));
                    break;

                case Constants.CONSTANT_CLASS:
                case Constants.CONSTANT_STRING:
                    bytes = new byte[Constants.u2];
                    System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;
                    contantPool.getContantPool().put(i,ByteUtil.byteToUnsignedShort(bytes));
                    break;

                case Constants.CONSTANT_FIELDREF:
                case Constants.CONSTANT_NAMEANDTYPE:
                case Constants.CONSTANT_INTERFACEMETHODREF:
                case Constants.CONSTANT_METHODREF:
                    bytes = new byte[Constants.u2];
                    System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;

                    int classIdx = ByteUtil.byteToUnsignedShort(bytes);

                    System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;

                    int nameTypeIdx = ByteUtil.byteToUnsignedShort(bytes);

                    contantPool.getContantPool().put(i,classIdx << 16 | nameTypeIdx );
                    break;

            case Constants.CONSTANT_METHODHANDLE:
                bytes = new byte[Constants.u1];
                System.arraycopy(content,idx,bytes,0,Constants.u1);idx += Constants.u1;
                contantPool.getContantPool().put(i,bytes[0]);
                bytes = new byte[Constants.u2];
                System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;
                contantPool.getContantPool().put(i,ByteUtil.byteToUnsignedShort(bytes));
                break;

            case Constants.CONSTANT_METHODTYPE:
                bytes = new byte[Constants.u2];
                System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;
                contantPool.getContantPool().put(i,ByteUtil.byteToUnsignedShort(bytes));
                break;

            case Constants.CONSTANT_INVOKEDYNAMIC:
                bytes = new byte[Constants.u2];
                System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;
                contantPool.getContantPool().put(i,ByteUtil.byteToUnsignedShort(bytes));
                System.arraycopy(content,idx,bytes,0,Constants.u2);idx += Constants.u2;
                contantPool.getContantPool().put(i,ByteUtil.byteToUnsignedShort(bytes));
                break;
            }
        }

        return idx;
        
    }

    public int setInterfaces( byte[] content, int cnt, int u2, int idx) {
        interfaces = new InterfaceInfo[cnt];
        byte[] u2Bytes = new byte[Constants.u2];
        for (int i = 0; i < cnt; i++) {
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            int classIdx = ByteUtil.byteToUnsignedShort(u2Bytes);
            System.out.println(i);
            interfaces[i] = new InterfaceInfo(
                    classIdx,(String)(contantPool.getContantPool().get(contantPool.getContantPool().get(classIdx)))
            );
        }
        return idx;

    }

    public int setField_info(byte[] content, int cnt, int u2, int idx) {
        byte[] u2Bytes = new byte[Constants.u2];
        field_info = new FieldInfo[cnt];
        for (int i = 0; i < cnt; i++) {
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            int accessFlags = ByteUtil.byteToUnsignedShort(u2Bytes);

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            int nameIdx = ByteUtil.byteToUnsignedShort(u2Bytes);

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            int descriptorIdx = ByteUtil.byteToUnsignedShort(u2Bytes);

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            int attributeCnt = ByteUtil.byteToUnsignedShort(u2Bytes);

            field_info[i] = new FieldInfo(
                    nameIdx,descriptorIdx,
                    (String)(contantPool.getContantPool().get(nameIdx)),
                    (String)(contantPool.getContantPool().get(descriptorIdx)),
                    accessFlags
            );
        }
        return idx;

    }

    public int setMethod_info(byte[] content, int cnt, int u2, int idx) {

        method_info = new MethodInfo[cnt];
        byte[] u2Bytes = new byte[Constants.u2];
        byte[] u4Bytes = new byte[Constants.u4];

        MethodInfo methodInfo ;
        for (int i = 0; i < cnt; i++) {
            method_info[i]= methodInfo = new MethodInfo();
            methodInfo.setKlass(this);
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            methodInfo.setAccessFlags(new AccessFlags(ByteUtil.byteToUnsignedShort(u2Bytes)));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            methodInfo.setMethodNameIdx(ByteUtil.byteToUnsignedShort(u2Bytes));
            methodInfo.setMethodName((String) contantPool.getContantPool().get(methodInfo.getMethodNameIdx()));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            methodInfo.setDescriptorIdx(ByteUtil.byteToUnsignedShort(u2Bytes));
            methodInfo.setDescriptor((String) contantPool.getContantPool().get(methodInfo.getDescriptorIdx()));

            int attrCnt = 0 ;
            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            methodInfo.setCodeAttributeCount(attrCnt = ByteUtil.byteToUnsignedShort(u2Bytes));
            idx = methodInfo.setCodeAttributes(content,attrCnt, Constants.u2,idx);

            // 判断是不是main函数
            String methodName = (String) this.getContantPool().getContantPool().get(methodInfo.getMethodNameIdx());
            String descriptorName = (String) this.getContantPool().getContantPool().get(methodInfo.getDescriptorIdx());
            if (methodName.equals("main") && descriptorName.equals("([Ljava/lang/String;)V")) {
                System.out.println("定位到main函数所在类");

                LoadClassFile.setMainKlass(this);
            }

        }
        return idx;
    }

    public void setAttributes_count(int attributes_count) {
        this.attributes_count = attributes_count;
        this.attribute_info = new AttributeInfo[attributes_count];
    }
}
